﻿using NewLife;
using NewLife.IO;
using NewLife.Log;
using NewLife.Threading;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ServerStart.Common
{
    /// <summary>应用服务管理</summary>
    public class ServiceManager : DisposeBase
    {
        #region 属性
        public ServiceInfo[] Services { get; set; }

        private CsvDb<ProcessInfo> _services;
        private readonly Dictionary<String, Process> _processes = new Dictionary<string, Process>();
        #endregion

        private TimerX _timer;
        #region 构造
        protected override void Dispose(Boolean disposing)
        {
            base.Dispose(disposing);

            Stop(disposing ? "Dispose" : "GC");
        }
        #endregion

        #region 方法
        public void Start()
        {
            // 等待全部完成

            foreach (var service in Services)
            {
                if (service.AutoStart)
                {
                    WriteLog("启动应用[{0}]：{1} {2} {3}", service.Name, service.FileName, service.Arguments, service.WorkingDirectory);

                    StartService(service);
                }
            }

            _timer = new TimerX(DoWork, null, 30_000, 30_000) { Async = true };
        }

        private Process StartService(ServiceInfo service)
        {
            WriteLog("启动应用[{0}]：{1} {2}", service.Name, service.FileName, service.Arguments);

            // 修正路径
            var workDir = "";
            var file = service.FileName;
            if (file.Contains("/") || file.Contains("\\"))
            {
                file = file.GetFullPath();
                workDir = Path.GetDirectoryName(file);
            }
            else
            {
                workDir = AppDomain.CurrentDomain.BaseDirectory;
                file = Path.Combine(workDir, file);
            }

            var si = new ProcessStartInfo
            {
                FileName = file,
                Arguments = service.Arguments,
                WorkingDirectory = workDir,
            };
            if (file.ToLower().EndsWith(".dll"))
            {
                si.FileName = "dotnet";
                si.Arguments = " " + file + " " + service.Arguments;
            }
            var retry = service.Retry;
            if (retry <= 0) retry = 1024;
            for (var i = 0; i < retry; i++)
            {
                try
                {
                    var p = Process.Start(si);

                    WriteLog("应用[{0}]启动成功 PID={1}", service.Name, p.Id);
                    // 记录进程信息，避免宿主重启后无法继续管理
                    _processes[service.Name] = p;
                    pi.Save(_services, p);
                    return p;
                }
                catch (Exception ex)
                {
                    Log?.Write(LogLevel.Error, "{0}", ex);

                    Thread.Sleep(5_000);
                }
            }

            return null;
        }
        private void DoWork(Object state)
        {
            foreach (var svc in Services)
            {
                if (svc != null && svc.AutoStart)
                {
                    if (_processes.TryGetValue(svc.Name, out var p))
                    {
                        if (svc.AutoRestart && (p == null || p.HasExited))
                        {
                            WriteLog("应用[{0}/{1}]已退出，准备重新启动！", svc.Name, p?.Id);

                            StartService(svc);
                        }
                    }
                    else
                    {
                        WriteLog("新增应用[{0}]，准备启动！", svc.Name);

                        StartService(svc);
                    }
                }
            }
        }

        public void Stop(String reason)
        {
            //foreach (var item in _processes)
            //{
            //    WriteLog("停止应用[{0}] PID={1} {2}", item.ProcessName, item.Id, reason);
            //    if (item.ProcessName == "dotnet")
            //    {
            //        item.Kill();
            //        continue;
            //    }
            //    //item.Kill();
            //    StopProcess(item.ProcessName, item.MainModule.FileName);
            //}
            _processes.Clear();
        }
        /// <summary>
        /// 停止程序
        /// </summary>
        /// <param name="name"></param>
        /// <param name="fullPath"></param>
        private void StopProcess(string name, string fullPath)
        {
            try
            {
                var ps = Process.GetProcessesByName(name);
                if (ps == null || ps.Length == 0)
                    return;
                for (int i = 0; i < ps.Length; i++)
                {
                    var p = ps[i];
                    if (p.MainModule.FileName.ToLower() == fullPath.ToLower())
                    {
                        p.Kill();
                    }
                }
            }
            catch (Exception ex)
            {
                XTrace.WriteLine("结束进程【" + name + "】失败。");
                XTrace.WriteException(ex);
            }
        }
        #endregion

        #region 日志
        public ILog Log { get; set; }

        public void WriteLog(String format, params Object[] args)
        {
            if (Log != null)
                Log.Info(format, args);
            else
                XTrace.WriteLine(format, args);
        }
        #endregion
        #region 辅助
        /// <summary>服务运行信息</summary>
        class ProcessInfo
        {
            public String Name { get; set; }

            public Int32 ProcessId { get; set; }

            public String ProcessName { get; set; }

            public DateTime CreateTime { get; set; }

            public DateTime UpdateTime { get; set; }

            public void Save(CsvDb<ProcessInfo> db, Process p)
            {
                var add = ProcessId == 0;

                ProcessId = p.Id;
                ProcessName = p.ProcessName;

                if (add) CreateTime = DateTime.Now;
                UpdateTime = DateTime.Now;

                if (add)
                    db.Add(this);
                else
                    db.Update(this);
            }
        }
        #endregion
    }
}
